package com.changgou.search.service.impl;

import com.alibaba.fastjson.JSON;
import com.changgou.entity.Result;
import com.changgou.goods.feign.SkuFeign;
import com.changgou.goods.pojo.Sku;
import com.changgou.search.dao.SkuEsMapper;
import com.changgou.search.pojo.SkuInfo;
import com.changgou.search.service.SkuService;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.bucket.terms.StringTerms;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortBuilders;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.util.*;

/**
 * Created by zhangyuhong
 * Date:2020/5/12
 */
@Service
public class SkuServiceImpl implements SkuService {

    @Autowired
    private SkuFeign skuFeign;

    @Autowired
    private SkuEsMapper skuEsMapper;

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;
    //默认的第一页
    Integer pageNum = 1;
    //默认每页大小
    Integer pageSize = 40;

    /**
     * 导入sku数据到es
     */
    @Override
    public void importSku() {
        //调用changgou-service-goods微服务
        Result<List<Sku>> skuListResult = skuFeign.findByStatus("1");
        //将数据转成search.Sku
        List<SkuInfo> skuInfos = JSON.parseArray(JSON.toJSONString(skuListResult.getData()), SkuInfo.class);
        for (SkuInfo skuInfo : skuInfos) {
            Map<String, Object> specMap = JSON.parseObject(skuInfo.getSpec());
            skuInfo.setSpecMap(specMap);
        }
        /*结果的specmap应该是这样的
        "specMap": {
            "手机屏幕尺寸": "5寸",
                    "网络": "联通2G",
                    "颜色": "绿",
                    "测试": "测试",
                    "机身内存": "16G",
                    "存储": "16G",
                    "像素": "800万像素"*/
        skuEsMapper.saveAll(skuInfos);
    }

    /**
     * @param searchMap
     * @return
     */
    //执行实现关键字搜索的功能
    @Override
    public Map search(Map<String, String> searchMap) {
/*传来的数据
{"keywords":"关键字","category":"种类","brand":"品牌",spec_规格名:"64英寸"
 ,"price":"100-200","sortField":"排序的字段price","sortRule":"DESC","pageNum":"1"}
 */
        //0 从页面中获取要搜索的内容关键字
        String keywords = searchMap.get("keywords");

        if (StringUtils.isEmpty(keywords)) {//如果为空
            keywords = "华为";
        }

        //1.创建一个查询对象的 构建对象
        NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();

        //1.1 设置分类分组条件  商品分类  两步  (设置分组条件(名字,查询字段,大小),获取分组的StringTerms获取里面的bucket的key)
        // addAggregation():增加聚合参数
        //skuCategorygroup:分组查询的别名
        //50显示多少个
        //AggregationBuilders表示聚合查询  field,是pojo字段的值,因为数据库导入到es的时候用的是pojo,所以es里的也是pojo了
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("skuCategorygroup").field("categoryName").size(50));
        //1.2 设置品牌分组
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("skuBrandgroup").field("brandName").size(50));
        //1.3 设置规格分组  spec.keyword:代表的是spec的一个域,keyword只是代表不分词
        nativeSearchQueryBuilder.addAggregation(AggregationBuilders.terms("skuSpecgroup").field("spec.keyword").size(5000));

        //2.1设置查询的条件  匹配查询 从哪个索引 哪一个类型中查询 不指定就查询所有的索引和所有的类型
        nativeSearchQueryBuilder.withQuery(QueryBuilders.matchQuery("name", keywords));//匹配查询 特点：先分词再查询
        //http://192.168.211.132:9200/_analyze?analyzer=ik_smart&pretty=true&text=我是程序员    查询分词情况
        //2.2设置组合查询
        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
        //加上了种类查询
        String category = searchMap.get("category");
        if (!StringUtils.isEmpty(category)) {
            //must必须要满足  must  must_not  should    filter(就是一定要满足)
            boolQueryBuilder.filter(QueryBuilders.termQuery("categoryName", category));
        }
        //2.3 设置品牌查询
        String brand = searchMap.get("brand");
        if (!StringUtils.isEmpty(brand)) {
            //相当于加入了条件 brandName=brand
            boolQueryBuilder.filter(QueryBuilders.termQuery("brandName", brand));
        }
        //2.4 设置多条件查询,因为这个是字段里的,key不是固定的,所有复杂
        for (String key : searchMap.keySet()) {
            if (key.startsWith("spec_")) {//就是规格相关的
                // specMap.规格名.keyword:规格选项值  (查询的时候带上keyword,不分词)
                boolQueryBuilder.filter(QueryBuilders.termQuery("specMap." + key.substring(5) + ".keyword", searchMap.get(key)));  //查询的字段,值
            }
        }
        //2.5 多条件组合过滤查询  价格的范围查询  500-1000   3000-*
        String price = searchMap.get("price");
        if (price != null) {
            String[] priceSplit = price.split("-");
            //注意:这里一定要转成Long才能进行比较,因为<>是比较数值的,不是String的
            String price1 = priceSplit[0];
            String price2 = priceSplit[1];
            if (price2.equals("*")) {
                //100-*  大于100的
                boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(price1));
            } else if (price1.equals("*")) {
                //*-100  小于100的
                boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").lte(price2));
            } else {
                //3000快以上 就是大于=
                boolQueryBuilder.filter(QueryBuilders.rangeQuery("price").gte(price1).lte(price2));
            }
        }
        //设置过滤的所有条件
        nativeSearchQueryBuilder.withFilter(boolQueryBuilder);

        //2.6 设置排序
        String sortField = searchMap.get("sortField");//price
        String sortRule = searchMap.get("sortRule");//DESC/ ASC
        if (!StringUtils.isEmpty(sortField) && !StringUtils.isEmpty(sortRule)) {
            ////设置排序 order by price desc  不是过滤的
            nativeSearchQueryBuilder.withSort(SortBuilders.fieldSort(sortField).order(SortOrder.valueOf(sortRule)));
        }
        //2.7 分页
        String pageNumStr = searchMap.get("pageNum");
        String pageSizeStr = searchMap.get("pageSize");
        if (!StringUtils.isEmpty(pageNumStr) ) {
            pageNum = Integer.parseInt(pageNumStr);
            if (!StringUtils.isEmpty(pageSizeStr)){
                pageSize= Integer.parseInt(pageSizeStr);
            }
        }
        Pageable pageable = PageRequest.of(pageNum - 1, pageSize);
        nativeSearchQueryBuilder.withPageable(pageable); //用于设置分页的条件

        //3.设置高亮
        nativeSearchQueryBuilder.withHighlightFields(new HighlightBuilder.Field("name"));

        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.preTags("<em>").postTags("</em>");
        nativeSearchQueryBuilder.withHighlightBuilder(highlightBuilder);

        //4.构建查询对象
        SearchQuery query = nativeSearchQueryBuilder.build();
        //5.执行查询 这个查询很强大,什么东西都在里面了
        //可以自己动态设置里面的数据(增加一个参数new SearchResultMapperImpl),获取高亮的数据就要这样
        AggregatedPage<SkuInfo> skuInfos = elasticsearchTemplate.queryForPage(query, SkuInfo.class,new SearchResultMapperImpl());

        //5.1 获取类别分组结果
        List<String> categoryList = getStringsCategoryList("skuCategorygroup", skuInfos);
        //5.2 获取品牌分组结果
        List<String> brandList = getStringsCategoryList("skuBrandgroup", skuInfos);
        //5.3 获取规格的分组
        StringTerms skuSpecgroupTerms = (StringTerms) skuInfos.getAggregation("skuSpecgroup");
        Map<String, Set<String>> specMap = getStringSetMap(skuSpecgroupTerms);

        //6.获取结果  没一条记录都是SkuInfo.class的对象
        List<SkuInfo> content = skuInfos.getContent();//获取当前页的所有的记录
        long totalElements = skuInfos.getTotalElements();//获取总记录数
        int totalPages = skuInfos.getTotalPages();//获取总页数
        //7.设置结果封装对象返回
        Map<String, Object> resultMap = new HashMap<String, Object>();
        resultMap.put("categoryList", categoryList);  //分组的列表
        resultMap.put("brandList", brandList);  //品牌列表
        resultMap.put("rows", content);//当前页的结果集
        resultMap.put("total", totalElements);//总记录数
        resultMap.put("totalPages", totalPages);//总页数
        resultMap.put("specMap", specMap);//设置规格列表的数据返回给前端
        resultMap.put("pageNum", pageNum);//
        resultMap.put("pageSize", pageSize);//
        return resultMap;
    }

    //获取规格的所有属性,就是{"电视音响效果"{"立体声","外放音"},,"电视屏幕尺寸"{"20英寸","24英寸"},"尺码"{"165","150"}}
    private Map<String, Set<String>> getStringSetMap(StringTerms skuSpecgroupTerms) {
        if (skuSpecgroupTerms == null) {
            return null;
        }
        Map<String, Set<String>> specMap = new HashMap<>();  //key:存规格名 value:所有的规格(63英寸,32英寸等)
        HashSet<String> values = null;
        List<StringTerms.Bucket> buckets = skuSpecgroupTerms.getBuckets();  //有一条一条的spec{key,value;"key1":value2....}
        for (StringTerms.Bucket bucket : buckets) {
            //{"电视音响效果":"立体声","电视屏幕尺寸":"20英寸","尺码":"165"} 循环的单条数据 字符串
            //{"电视音响效果":"立体声22","电视屏幕尺寸":"20英寸22","尺码":"16522"} 循环的单条数据 字符串
            String keyAsString = bucket.getKeyAsString();  //电视音响效果   电视屏幕尺寸等key
            Map<String, String> map = JSON.parseObject(keyAsString, Map.class);
            for (String key : map.keySet()) {
                if (specMap.get(key) == null) {
                    //放在这里(没有specMap.get(key) == null判断),第二次bucket循环的时候,会把前面一个覆盖了,所有要判断是不是第一个,第一个就new,其他的就不需要了
                    values = new HashSet<>();
                }
                values.add(map.get(key));
                specMap.put(key, values);

            }
        }
        return specMap;
    }

    /**
     * 获取分类列表数据
     *
     * @return
     */
//   StringTerms:  TermsAggregator 的结果集,封装到了Buckets.
    private List<String> getStringsCategoryList(String groupName, AggregatedPage<SkuInfo> skuInfos) {
        StringTerms stringTerms = (StringTerms) skuInfos.getAggregation(groupName);  //stringTerms里的Buckets已经存储好了查询出来的值,为key
        List<String> categoryList = new ArrayList<>();
        if (stringTerms != null) {
            for (StringTerms.Bucket bucket : stringTerms.getBuckets()) {
                String keyAsString = bucket.getKeyAsString();//就是分类的名称,categoryName的字段的key,手机配件
                categoryList.add(keyAsString);
            }
        }
        return categoryList;
    }
}
